/**
  ******************************************************************************
  * @file    bsp_nand_ex.c
  * @author  Iron
  * @date    2018-07-09
  * @version v1.0
  * @brief   bsp_nand_ex c file
  */

/** @addtogroup GROUP_BSP
  * @{
  */

/* includes ------------------------------------------------------------------*/
#include <string.h>
#include "stm32f4xx_hal.h"
#include "bsp_nand.h"
#include "bsp_nand_ex.h"

/* BEBUG LOG */
#include "debug_log.h"
#define LOG_LOCAL_LEVEL DBG_LOG_INFO // DBG_LOG_INFO  DBG_LOG_DEBUG
DBG_LOG_TAG("NAND_EX");

/* private typedef -----------------------------------------------------------*/
/* private macro -------------------------------------------------------------*/
#define BSP_NAND_PAR_HD_BLOCKS    768  // 96 MB
#define BSP_NAND_PAR_LOG_BLOCKS   128  // 16 MB
#define BSP_NAND_PAR_FILE_BLOCKS  128  // 16 MB

#define BSP_NAND_PAR_HD_START     0
#define BSP_NAND_PAR_HD_END      (BSP_NAND_PAR_HD_START + BSP_NAND_PAR_HD_BLOCKS - 1)

#define BSP_NAND_PAR_LOG_START   (BSP_NAND_PAR_HD_END + 1)
#define BSP_NAND_PAR_LOG_END     (BSP_NAND_PAR_LOG_START + BSP_NAND_PAR_LOG_BLOCKS - 1)

#define BSP_NAND_PAR_FILE_START  (BSP_NAND_PAR_LOG_END + 1)
#define BSP_NAND_PAR_FILE_END    (BSP_NAND_PAR_FILE_START + BSP_NAND_PAR_FILE_BLOCKS - 1)

/* private variables ---------------------------------------------------------*/
const bsp_nand_partition_t nand_par_default =
{
    .page_size =       BSP_NAND_PAGE_DATA_SIZE,
    .pages_per_block = BSP_NAND_PAGES_PER_BLOCK,
    .spare_size =      BSP_NAND_PAGE_SPARE_SIZE,
    .spare_free =      BSP_NAND_PAGE_SPARE_SIZE - BSP_NAND_ECC_SIZE,
    .spare_block_status_offs = BSP_NAND_ECC_SIZE,
    .block_start =     0,
    .block_end =       BSP_NAND_TOTAL_BLOCKS - 1,
    .block_total =     BSP_NAND_TOTAL_BLOCKS
};

const bsp_nand_partition_t nand_par_hd_data =
{
    .page_size =       BSP_NAND_PAGE_DATA_SIZE,
    .pages_per_block = BSP_NAND_PAGES_PER_BLOCK,
    .spare_size =      BSP_NAND_PAGE_SPARE_SIZE,
    .spare_free =      BSP_NAND_PAGE_SPARE_SIZE - BSP_NAND_ECC_SIZE,
    .spare_block_status_offs = BSP_NAND_ECC_SIZE,
    .block_start =     BSP_NAND_PAR_HD_START,
    .block_end =       BSP_NAND_PAR_HD_END,
    .block_total =     BSP_NAND_PAR_HD_BLOCKS
};

const bsp_nand_partition_t nand_par_log =
{
    .page_size =       BSP_NAND_PAGE_DATA_SIZE,
    .pages_per_block = BSP_NAND_PAGES_PER_BLOCK,
    .spare_size =      BSP_NAND_PAGE_SPARE_SIZE,
    .spare_free =      BSP_NAND_PAGE_SPARE_SIZE - BSP_NAND_ECC_SIZE,
    .spare_block_status_offs = BSP_NAND_ECC_SIZE,
    .block_start =     BSP_NAND_PAR_LOG_START,
    .block_end =       BSP_NAND_PAR_LOG_END,
    .block_total =     BSP_NAND_PAR_LOG_BLOCKS
};

const bsp_nand_partition_t nand_par_file =
{
    .page_size =       BSP_NAND_PAGE_DATA_SIZE,
    .pages_per_block = BSP_NAND_PAGES_PER_BLOCK,
    .spare_size =      BSP_NAND_PAGE_SPARE_SIZE,
    .spare_free =      BSP_NAND_PAGE_SPARE_SIZE - BSP_NAND_ECC_SIZE,
    .spare_block_status_offs = BSP_NAND_ECC_SIZE,
    .block_start =     BSP_NAND_PAR_FILE_START,
    .block_end =       BSP_NAND_PAR_FILE_END,
    .block_total =     BSP_NAND_PAR_FILE_BLOCKS
};

/* private function prototypes -----------------------------------------------*/
/* private functions ---------------------------------------------------------*/

int32_t bsp_nand_flash_read(const bsp_nand_partition_t *partition, uint32_t addr, uint8_t *data, uint32_t data_len)
{
    uint32_t columm, row, rblock;
    int32_t res = BSP_NAND_EX_OK;

    columm = addr % partition->page_size;  // A0~A11
    row = addr / partition->page_size;     // A12~A27
    rblock = row / partition->pages_per_block;
    if (rblock < partition->block_start || rblock > partition->block_end)
    {
        DBG_LOGE(TAG, "Nand flash read page block %d row %d columm %d invalid.", rblock, row, columm);
        return BSP_NAND_EX_FAIL;
    }

    BSP_NAND_RTOS_MUTEX_WAIT();

    if (data && data_len > 0)
    {
        NAND_CMD(NAND_CMD_AREA_A);
        NAND_ADDR(columm);                  // Column Address
        NAND_ADDR(columm >> 8);             // Column Address
        NAND_ADDR(row & 0xFF);              // Row Address
        NAND_ADDR((row >> 8) & 0xFF);       // Row Address
        NAND_CMD(NAND_CMD_AREA_TRUE1);
        /* TWB 100us */
        bsp_nand_wait_rb(NAND_RB_RESET, 100);
        /* TR 40us*/
        bsp_nand_wait_rb(NAND_RB_SET, 40);

#ifdef NAND_DMA_ENABLE
        bsp_nand_dma_read(data, data_len);
#else
        for (i = 0; i < data_len; i++)
        {
            data[i] = NAND_DATA_READ_BYTE();
        }
#endif

        DBG_LOGD(TAG, "Nand flash read block %d row %d columm %d data length %d.", rblock, row, columm, data_len);
    }

    BSP_NAND_RTOS_MUTEX_RELEASE();
    return res;
}

int32_t bsp_nand_flash_write(const bsp_nand_partition_t *partition, uint32_t addr, const uint8_t *data, uint32_t data_len)
{
    uint32_t columm, row, wblock;
    int32_t res = BSP_NAND_EX_OK;

    columm = addr % partition->page_size;
    row = addr / partition->page_size;
    wblock = row / partition->pages_per_block;
    if (wblock < partition->block_start || wblock > partition->block_end)
    {
        DBG_LOGE(TAG, "Nand flash write page block %d row %d columm %d invalid.", wblock, row, columm);
        return BSP_NAND_EX_FAIL;
    }

    BSP_NAND_RTOS_MUTEX_WAIT();

    if (data && data_len)
    {
        NAND_CMD(NAND_CMD_WRITE0);        // 0x80
        NAND_ADDR(columm);                // Column Address
        NAND_ADDR(columm >> 8);           // Column Address
        NAND_ADDR(row);                   // Row Address
        NAND_ADDR(row >> 8);              // Row Address

#ifdef NAND_DMA_ENABLE
        bsp_nand_dma_write(data, data_len);
#else
        for (i = 0; i < data_len; i++)
        {
            NAND_DATA_WRITE_BYTE(data[i]);
        }
#endif

        NAND_CMD(NAND_CMD_WRITE_TRUE1); // 0x10
        /* TWB 100us */
        bsp_nand_wait_rb(NAND_RB_RESET, 100);
        /* TPROG 400us / max 900us*/
        bsp_nand_wait_rb(NAND_RB_SET, 900);

        if (bsp_nand_read_status() & NAND_ERROR)
        {
            bsp_nand_reset();
            res = BSP_NAND_EX_FAIL;
            DBG_LOGE(TAG, "Nand flash program data in block %d row %d columm %d error.", wblock, row, columm);
        }
        else
        {
            DBG_LOGD(TAG, "Nand flash write block %d row %d columm %d data length %d.", wblock, row, columm, data_len);
        }
    }

    BSP_NAND_RTOS_MUTEX_RELEASE();
    return res;
}

int32_t bsp_nand_flash_erase_block(const bsp_nand_partition_t *partition, uint32_t block)
{
    int32_t res = BSP_NAND_EX_OK;
    uint32_t row, eblock;

    if (partition == NULL) return BSP_NAND_EX_FAIL;

    eblock = partition->block_start + block;
    row = eblock * partition->pages_per_block;
    if (eblock < partition->block_start || eblock > partition->block_end)
    {
        DBG_LOGE(TAG, "Nand flash erase page block %d (page %d) invalid.", eblock, row);
        return BSP_NAND_EX_FAIL;
    }

    BSP_NAND_RTOS_MUTEX_WAIT();

    NAND_CMD(NAND_CMD_ERASE0); // 0x60
    NAND_ADDR(row);            // Row Address
    NAND_ADDR(row >> 8);       // Row Address
    NAND_CMD(NAND_CMD_ERASE1); // 0xD0

    /* TWB 100us */
    bsp_nand_wait_rb(NAND_RB_RESET, 100);
    /* tBERS 4.5ms / max 16ms*/
    bsp_nand_wait_rb(NAND_RB_SET, 16000);

    if (bsp_nand_read_status() & NAND_ERROR)
    {
        bsp_nand_reset();
        res = BSP_NAND_EX_FAIL;
        DBG_LOGE(TAG, "Nand flash erase block %d (row:%d) error.", eblock, row);
    }
    else
    {
        DBG_LOGD(TAG, "Nand flash erase block %d (row:%d).", eblock, row);
    }

    BSP_NAND_RTOS_MUTEX_RELEASE();
    return res;
}

int32_t bsp_nand_flash_read_page(const bsp_nand_partition_t *partition, uint32_t page,
                                 uint8_t *data,  uint32_t data_len, uint8_t *spare, uint32_t spare_len)
{
    int32_t res = BSP_NAND_EX_OK;
    uint32_t i, rblock, rpage;
    uint32_t gecc, recc;

    if (partition == NULL) return BSP_NAND_EX_FAIL;

    rpage = partition->block_start * partition->pages_per_block + page;
    rblock = rpage / partition->pages_per_block;
    if (rblock < partition->block_start || rblock > partition->block_end)
    {
        DBG_LOGE(TAG, "Nand flash read page block %d (page %d) invalid.", rblock, rpage);
        return BSP_NAND_EX_FAIL;
    }

    BSP_NAND_RTOS_MUTEX_WAIT();

    if (data && data_len > 0)
    {
        NAND_CMD(NAND_CMD_AREA_A);
        NAND_ADDR(0x00);                                // Column Address
        NAND_ADDR(0x00);                        // Column Address
        NAND_ADDR(rpage & 0xFF);          // Row Address
        NAND_ADDR((rpage >> 8) & 0xFF);   // Row Address
        NAND_CMD(NAND_CMD_AREA_TRUE1);
        /* TWB 100us */
        bsp_nand_wait_rb(NAND_RB_RESET, 100);
        /* TR 40us*/
        bsp_nand_wait_rb(NAND_RB_SET, 40);

        NAND_ECC_ENABLE();
#ifdef NAND_DMA_ENABLE
        bsp_nand_dma_read(data, data_len);
#else
        for (i = 0; i < data_len; i++)
        {
            data[i] = NAND_DATA_READ_BYTE();
        }
#endif
        NAND_ECC_GET_VALUE(&gecc);
        NAND_ECC_DISABLE();

        // ECC sprae 前 4 字节
        if (data_len == partition->page_size)
        {
            uint8_t tmp[4];
            for (i = 0; i < BSP_NAND_ECC_SIZE; i++)
            {
                tmp[i] = NAND_DATA_READ_BYTE();
            }
            if (spare && spare_len)
            {
                for (i = 0; i < (spare_len - BSP_NAND_ECC_SIZE); i++)
                {
                    spare[BSP_NAND_ECC_SIZE + i] = NAND_DATA_READ_BYTE();
                }
                memcpy(spare, tmp, BSP_NAND_ECC_SIZE);
            }
            recc  = (tmp[3] << 24) | (tmp[2] << 16) | (tmp[1] << 8) | tmp[0];
            if (recc != 0xFFFFFFFF && gecc != 0)
            {
                res = bsp_nand_data_correct(gecc, recc, data);
            }
            if (res != BSP_NAND_OK)
            {
                res = BSP_NAND_EX_FAIL;
                DBG_LOGE(TAG, "Nand flash read page: %d ECC error, gecc %08X, recc %08X>", rpage, gecc, recc);
            }
            else
            {
                res = BSP_NAND_EX_OK;
            }
        }

        DBG_LOGD(TAG, "Nand flash read Page data from block %d page %d (page: %d) data length %d.", rblock, rpage, page, data_len);
        goto _EXIT;
    }

    if (spare && spare_len)
    {
        NAND_CMD(NAND_CMD_AREA_A);
        NAND_ADDR(partition->page_size);         // Column Address
        NAND_ADDR(partition->page_size >> 8);    // Column Address
        NAND_ADDR(rpage & 0xFF);                 // Row Address
        NAND_ADDR((rpage >> 8) & 0xFF);          // Row Address
        NAND_CMD(NAND_CMD_AREA_TRUE1);
        /* TWB 100us */
        bsp_nand_wait_rb(NAND_RB_RESET, 100);
        /* TR 40us*/
        bsp_nand_wait_rb(NAND_RB_SET, 40);
        for (i = 0; i < spare_len; i++)
        {
            spare[i] = NAND_DATA_READ_BYTE();
        }
        DBG_LOGD(TAG, "Nand flash read Spare data from block %d page %d (page: %d) spare length %d.", rblock, rpage, page, spare_len);
    }

_EXIT:
    BSP_NAND_RTOS_MUTEX_RELEASE();
    return res;
}

int32_t bsp_nand_flash_write_page(const bsp_nand_partition_t *partition, uint32_t page,
                                  const uint8_t *data,  uint32_t data_len, const uint8_t *spare, uint32_t spare_len)
{
    int32_t res = BSP_NAND_EX_OK;
    uint32_t i, wblock, wpage;
    uint32_t gecc;

    if (partition == NULL) return BSP_NAND_EX_FAIL;

    wpage = partition->block_start * partition->pages_per_block + page;
    wblock = wpage / partition->pages_per_block;
    if (wblock < partition->block_start || wblock > partition->block_end)
    {
        DBG_LOGE(TAG, "Nand flash read page block %d (page %d) invalid.", wblock, wpage);
        return BSP_NAND_EX_FAIL;
    }

    BSP_NAND_RTOS_MUTEX_WAIT();

    if (data && data_len)
    {
        NAND_CMD(NAND_CMD_WRITE0); // 0x80
        NAND_ADDR(0x00);                                // Column Address
        NAND_ADDR(0x00 >> 8);                       // Column Address
        NAND_ADDR(wpage & 0xFF);           // Row Address
        NAND_ADDR((wpage >> 8) & 0xFF);    // Row Address

        NAND_ECC_ENABLE();
#ifdef NAND_DMA_ENABLE
        bsp_nand_dma_write(data, data_len);
#else
        for (i = 0; i < data_len; i++)
        {
            NAND_DATA_BYTE_W(data[i]);
        }
#endif
        NAND_ECC_GET_VALUE(&gecc);
        NAND_ECC_DISABLE();

        // write ECC
        if (data_len == partition->page_size)
        {
            NAND_DATA_WRITE_BYTE((uint8_t) gecc);
            NAND_DATA_WRITE_BYTE((uint8_t)(gecc >> 8));
            NAND_DATA_WRITE_BYTE((uint8_t)(gecc >> 16));
            NAND_DATA_WRITE_BYTE((uint8_t)(gecc >> 24));

            if (spare && spare_len)
            {
                for (i = 0; i < (spare_len - BSP_NAND_ECC_SIZE); i++)
                {
                    NAND_DATA_WRITE_BYTE(spare[BSP_NAND_ECC_SIZE + i]);
                }
            }
        }

        NAND_CMD(NAND_CMD_WRITE_TRUE1); // 0x10
        /* TWB 100us */
        bsp_nand_wait_rb(NAND_RB_RESET, 100);
        /* TPROG 400us / max 900us*/
        bsp_nand_wait_rb(NAND_RB_SET, 900);

        if (bsp_nand_read_status() & NAND_ERROR)
        {
            bsp_nand_reset();
            res = BSP_NAND_EX_FAIL;
            DBG_LOGE(TAG, "Nand flash program data to block %d page %d (page: %d) error.", wblock, wpage, wpage);
        }
        else
        {
            res = BSP_NAND_EX_OK;
            DBG_LOGD(TAG, "Nand flash write Page data to block %d page %d (page: %d) data length %d.", wblock, wpage, page, data_len);
        }
        goto _EXIT;
    }

    if (spare && spare_len)
    {
        NAND_CMD(NAND_CMD_WRITE0);
        NAND_ADDR(BSP_NAND_PAGE_DATA_SIZE & 0xFF);  // Column Address
        NAND_ADDR(BSP_NAND_PAGE_DATA_SIZE >> 8);    // Column Address
        NAND_ADDR(wpage & 0xFF);           // Row Address
        NAND_ADDR((wpage >> 8) & 0xFF);    // Row Address
        for (i = 0; i < spare_len; i++)
        {
            NAND_DATA_WRITE_BYTE(spare[i]);
        }
        NAND_CMD(NAND_CMD_WRITE_TRUE1); // 0x10

        /* TWB 100us */
        bsp_nand_wait_rb(NAND_RB_RESET, 100);
        /* TPROG 400us / max 900us*/
        bsp_nand_wait_rb(NAND_RB_SET, 900);

        if (bsp_nand_read_status() & NAND_ERROR)
        {
            bsp_nand_reset();
            res = BSP_NAND_EX_FAIL;
            DBG_LOGE(TAG, "Nand flash write spare to block %d page %d (page: %d) error.", wblock, wpage, page);
        }
        else
        {
            res = BSP_NAND_EX_OK;
            DBG_LOGD(TAG, "Nand flash write Spare data to block %d page %d (wpage: %d) spare length %d.", wblock, page, wpage, spare_len);
        }
    }

_EXIT:
    BSP_NAND_RTOS_MUTEX_RELEASE();
    return res;
}

/**
  * @}
  */

/******************* (C)COPYRIGHT 2018 ***** END OF FILE *********************/
